package tk.pak0.audiomidifier.model.audio;

import java.util.ArrayList;
import java.util.List;

import javax.sound.sampled.*;
import javax.sound.sampled.Mixer.Info;

import org.apache.log4j.Logger;

import com.petersalomonsen.jjack.javasound.SourceJJackLine;
import com.petersalomonsen.jjack.javasound.TargetJJackLine;

/*
podemos utilizar también la información que nos dan los armónicos, el primer armónico será el que nos confirme 
la fundamental, en incluso, ayude al programa a oir frecuencias por debajo de su threshold fijándose que le segundo 
valor mayor es el primer armónico, es decir, si el primer valor mas alto es el fftFoo[i], el segundo corresponderá a
la octava, que puede haber dos casos, que la frecuencia de este segundo valor mayor sea el doble justo, o que no 
coincida, en cuyo caso podría tratarse de una frecuencia por debajo del threshold, en ese caso, sería la octava la que
nos diría la frecuencia original, de esa manera, el programa podrá oir frecuencias por debajo de su threshold. Se podría
activar un flag, para decirle a procesos posteriores lo que ocurre, que se llame subPitch
*/

/**
 * @author Francisco Fuentes Barrera
 */
public class AudioTools {
	private static Logger log = Logger.getLogger(AudioTools.class); 
	
    float frec = 8000.0F; //controlamos la frecuencia
    int depth = 16; //la profundidad de bits
    int channels = 1;// los canales
    boolean bigEndian = true; // endianess
    boolean signed = true; // signed
    AudioFormat format; //creamos el formato de audio
    
    private Info[] mixersInfo;
	private Mixer[] mixers;
	private Line.Info[][] lineasEntradasInfo;
	private Line[][] lineasEntradas;
	private Line lineaEntradaSel;
	
	private Line.Info[][] lineasSalidasInfo;
	private Line[][] lineasSalidas;
	private Line lineaSalidaSel;
	
	private Integer[] sampleRates;
	private Integer[] resampleRates;
	private Integer[] bufferSizes;
	
	private static AudioTools instance=null;
	
	public static AudioTools getInstance() throws LineUnavailableException{
		if(instance == null)
			instance = new AudioTools();
		return instance;
	}
	
    //Setters & Getters
	
	public boolean isBigEndian() {
		return bigEndian;
	}

	public void setBigEndian(boolean bigEndian) {
		this.bigEndian = bigEndian;
	}

	public boolean isSigned() {
		return signed;
	}

	public void setSigned(boolean signed) {
		this.signed = signed;
	}

	public AudioFormat getFormat() {
		return format;
	}

	public void setFormat(AudioFormat format) {
		this.format = format;
	}

	public Mixer[] getMixers() {
		return mixers;
	}
	
	public void setMixers(Mixer[] mixers) {
		this.mixers = mixers;
	}
	
	public Info[] getMixersInfo() {
		return mixersInfo;
	}
	
	public void setMixersInfo(Info[] mixersInfo) {
		this.mixersInfo = mixersInfo;
	}
	
	public void setLineasEntradas(Line[][] lineasEntradas) {
		this.lineasEntradas = lineasEntradas;
	}
	
	public Line[][] getLineasEntradas() {
		return lineasEntradas;
	}
	
	public void setLineasEntradasInfo(Line.Info[][] lineasEntradasInfo) {
		this.lineasEntradasInfo = lineasEntradasInfo;
	}
	
	public Line.Info[][] getLineasEntradasInfo() {
		return lineasEntradasInfo;
	}
	
	public Line.Info[][] getLineasSalidasInfo() {
		return lineasSalidasInfo;
	}

	public void setLineasSalidasInfo(Line.Info[][] lineasSalidasInfo) {
		this.lineasSalidasInfo = lineasSalidasInfo;
	}

	public Line[][] getLineasSalidas() {
		return lineasSalidas;
	}

	public void setLineasSalidas(Line[][] lineasSalidas) {
		this.lineasSalidas = lineasSalidas;
	}

	public void setFrec(float f){ frec = f; }
    public float getFrec() { return frec; }

    public void setDepth(int b) { depth = b; }
    public int getDepth() { return depth; }

    public void setChannels(int c) { channels = c;}
    public int getChannels() { return channels; }
    
	public Integer[] getSampleRates() {
		return sampleRates;
	}

	public void setSampleRates(Integer[] sampleRates) {
		this.sampleRates = sampleRates;
	}

	public Integer[] getResampleRates() {
		return resampleRates;
	}

	public void setResampleRates(Integer[] resampleRates) {
		this.resampleRates = resampleRates;
	}

	public Integer[] getBufferSizes() {
		return bufferSizes;
	}

	public void setBufferSizes(Integer[] bufferSizes) {
		this.bufferSizes = bufferSizes;
	}

    public Line getLineaEntradaSel() {
		return lineaEntradaSel;
	}

	public void setLineaEntradaSel(Line lineaEntradaSel) {
		this.lineaEntradaSel = lineaEntradaSel;
	}

	public Line getLineaSalidaSel() {
		return lineaSalidaSel;
	}

	public void setLineaSalidaSel(Line lineaSalidaSel) {
		this.lineaSalidaSel = lineaSalidaSel;
	}

	void redoFormat(){ format = new AudioFormat(frec,depth,channels,true,false); }
    
    private AudioTools() throws LineUnavailableException{
    	mixersInfo = initInfoMixers();
		mixers = initMixers();
		
		lineasEntradasInfo = initLineasEntradaInfo();
		purgeMixers();
		
		lineasSalidasInfo = initLineasSalidaInfo();
		purgeMixers();
		
		lineasEntradas = initLineasEntrada();
		lineasSalidas = initLineasSalida();
		
		sampleRates = initSampleRates();
		resampleRates = initResampleRates();
		bufferSizes = initBufferSizes();
    }
    
    public Integer[] initSampleRates(){
    	return new Integer[]{8000,11025,16000,22050,32000,44100};
    }
    
    public Integer[] initResampleRates(){
//    	return new Integer[]{8000,11025,16000,22050,32000,44100};
    	return new Integer[]{128,256,512,1024,2048};
    }
    
    public Integer[] initBufferSizes(){
    	return new Integer[]{128,256,512,1024,2048};
    }

	public Mixer.Info [] initInfoMixers(){
        Mixer.Info [] mesas = AudioSystem.getMixerInfo();
        return mesas;
    }
    
    public Mixer[] initMixers(){
    	Mixer[] mixers = new Mixer[mixersInfo.length];
    	for(int i=0; i < mixers.length ; i++ ){
    		mixers[i] = getMixerFromInfo(mixersInfo[i]);
    	}
    	return mixers;
    }

    public Mixer getMixerFromInfo(Mixer.Info minfo){
        Mixer mesaL=null;
        try{
            mesaL = AudioSystem.getMixer(minfo);
        }catch (Exception e){
            System.err.println("Error al obtener la mesa: "+e);
        }
        
        return mesaL;
    }

    Line.Info [] getInfoLineas(Mixer mesa){
        Line.Info[] info = mesa.getTargetLineInfo();
        return info;
    }

//    void asignaLinea(Line.Info linfo){
//        TargetDataLine lineal =null;
//        TargetDataLine [] lineas = (TargetDataLine[])mesa.getTargetLines();
//        for (int i=0;i<lineas.length;i++){
//            if(linfo.equals(lineas[i].getLineInfo()) == true){
//                lineal=lineas[i];
//            }
//        }
//        if(lineal != null)
//            linea=lineal;
//    }

	Line.Info[][] initLineasEntradaInfo(){
		Line.Info[][] lineasEntradasInfoRet = new Line.Info[mixers.length][];
		for(int i=0; i< mixers.length; i++){
			lineasEntradasInfoRet[i] = mixers[i].getTargetLineInfo();
			List<Line.Info> listaDef = new ArrayList<Line.Info>();
			for(int j=0; j < lineasEntradasInfoRet[i].length; j++){
				try{
				
					Line testLine = mixers[i].getLine(lineasEntradasInfoRet[i][j]);
					if(((testLine instanceof TargetDataLine) && !(testLine instanceof Port)) || (testLine instanceof TargetJJackLine)){
						TargetDataLine testLineTarget = (TargetDataLine) testLine;
						log.debug("Entrada TargetDataLine: "+testLineTarget.toString());
						listaDef.add(lineasEntradasInfoRet[i][j]);
					}
				
				}catch (Exception e){
					e.printStackTrace();
				}
			}
			lineasEntradasInfoRet[i] = (Line.Info[]) listaDef.toArray(new Line.Info[listaDef.size()]);
			if(listaDef.size() <=0){
				mixers[i]=null;
			}
		}
		
		return lineasEntradasInfoRet;
	}
	
	Line.Info[][] initLineasSalidaInfo(){
		Line.Info[][]  lineasSalidasInfoRet = new Line.Info[mixers.length][];
		for(int i=0; i< mixers.length; i++){
			lineasSalidasInfoRet[i] = mixers[i].getSourceLineInfo();
			List<Line.Info> listaDef = new ArrayList<Line.Info>();
			for(int j=0; j < lineasSalidasInfoRet[i].length; j++){
				try{
				
					Line testLine = mixers[i].getLine(lineasSalidasInfoRet[i][j]);
					if(((testLine instanceof SourceDataLine) && !(testLine instanceof Port)) || (testLine instanceof SourceJJackLine)){
						SourceDataLine testLineSource = (SourceDataLine) testLine;
						log.debug("Salida SourceDataLine: "+testLineSource.toString());
						listaDef.add(lineasSalidasInfoRet[i][j]);
					}
				
				}catch (Exception e){
					e.printStackTrace();
				}
			}
			lineasSalidasInfoRet[i] = (Line.Info[]) listaDef.toArray(new Line.Info[listaDef.size()]);
			if(listaDef.size() <=0){
				mixers[i]=null;
			}
		}
		
		return lineasSalidasInfoRet;
	}
	
	void purgeMixers(){
		List<Mixer> mixersDef = new ArrayList<Mixer>();
		List<Mixer.Info> mixersInfoDef = new ArrayList<Mixer.Info>();
		for (int i=0; i < mixers.length; i++){
			if(mixers[i] != null){
				mixersDef.add(mixers[i]);
				mixersInfoDef.add(mixersInfo[i]);
			}
		}
		mixers = mixersDef.toArray(new Mixer[mixersDef.size()]);
		mixersInfo = mixersInfoDef.toArray(new Mixer.Info[mixersInfoDef.size()]);
	}
	
	Line[][] initLineasEntrada() throws LineUnavailableException{
		Line[][] lineasEntradas = new Line[mixers.length][];
		for(int i=0; i< mixers.length; i++){
			lineasEntradas[i] = new Line[lineasEntradasInfo[i].length];
			for(int j=0; j< lineasEntradasInfo[i].length; j++){
				lineasEntradas[i][j] = mixers[i].getLine(lineasEntradasInfo[i][j]);
			}
		}
		
		return lineasEntradas;
	}
	
	Line[][] initLineasSalida() throws LineUnavailableException{
		Line[][] lineasEntradas = new Line[mixers.length][];
		for(int i=0; i< mixers.length; i++){
			lineasEntradas[i] = new Line[lineasSalidasInfo[i].length];
			for(int j=0; j< lineasSalidasInfo[i].length; j++){
				lineasEntradas[i][j] = mixers[i].getLine(lineasSalidasInfo[i][j]);
			}
		}
		
		return lineasEntradas;
	}

    public double[] crearHannWindow(int size){
    	double[] hann = new double[size]; //debería de cambiar la precisión a byte?
        for(int i=1; i<=size; i++){
            hann[i-1]=(0.5*(1 - Math.cos((2*Math.PI*i)/(size-1))));
        }
        return hann;
    }
}
